#include "MD5Anim.h"
#ifdef USE_MD5MESH
#ifdef WEEK_2_CODE
MD5Anim::MD5Anim(std::string filename)	{
	numAnimatedComponents = 0;
	frameRate	= 0;
	numJoints	= 0;
	numFrames	= 0;
	joints		= NULL;
	bounds		= NULL;
	frames		= NULL;

	LoadMD5Anim(filename);
}

/*
Destructor simply deletes our heap memory
*/
MD5Anim::~MD5Anim(void)	{
	delete[] joints;
	delete[] bounds;
	delete[] frames;
}
/************************************************************************/
/*                                                                      */
/************************************************************************/
void MD5Anim::LoadMD5Anim( std::string filename )	{
	//The MD5Anim is human readable, and stores its data in an easily
	//traversable way, so we can simply stream data from the file
	std::ifstream f(filename,std::ios::in);	

	if(!f) {	//Opening the file has failed :(
		return;
	}

	//We have our MD5 file handle!
	unsigned int md5Version		 = 0;
	unsigned int numLoadedFrames = 0;
	unsigned int numLoadedJoints = 0;
	unsigned int numLoadedBounds = 0;

	/*
	Now we simply load in the data, while there's still data to load
	*/
	while(!f.eof()) {
		std::string currentLine;	//A temporary string to keep the current line in
		f >> currentLine;			//stream the next line of the file in

		if(currentLine.empty()) {	//Actually, there's nothing in this line...usually
			continue;				//because we've hit the end of the file.
		}
		/*
		String::Find returns a value, equating to the position in the string the search 
		string is - or the special value 'npos' if the searchstring is not found at all
		*/
		else if(currentLine.find(MD5_VERSION_TAG) != std::string::npos) {
			//We've found the MD5 version string!

			//ifstream allows us to stream ints,floats etc into variables
			f >> md5Version;
			std::cout << "MD5 File version is: " << md5Version << std::endl;
		}
		else if(currentLine.find(MD5_COMMANDLINE_TAG) != std::string::npos) {
			/*
			MD5Anim files sometimes have a 'command line' value, used by the game
			toolchain to generate some data. We don't care about it!
			*/
			std::cout << "Ignoring commandline value" << std::endl;
		}
		else if(currentLine.find(MD5_ANIM_NUMFRAMES) != std::string::npos) {
			f >> numFrames;	//Loading in the number of frames held in this MD5Anim file
			std::cout << "Expecting file to have " << numFrames << " frames" << std::endl;

			//If we have an incorrectly generated MD5Anim file, this might go wrong, as
			//there might be more frames than we've generated space for...
			bounds = new MD5Bounds[numFrames];
			frames = new MD5Frame [numFrames];
		}
		else if(currentLine.find(MD5_NUMJOINTS_TAG) != std::string::npos) {
			f >> numJoints;	//Loading in the number of joints in this MD5Anim file
			std::cout << "Expecting file to have " << numJoints << " joints" << std::endl;

			joints = new MD5AnimJoint[numJoints];
		}
		else if(currentLine.find(MD5_ANIM_FRAMERATE) != std::string::npos) {
			f >> frameRate; //Loading in the framerate of this anim
		}
		else if(currentLine.find(MD5_ANIM_COMPONENTS) != std::string::npos) {
			f >> numAnimatedComponents;	//Loading in the number of delta values in this anim
		}
		else if(currentLine.find(MD5_ANIM_HIERARCHY) != std::string::npos) {
			LoadMD5AnimHierarchy(f, numLoadedJoints);	//Let's load in the joint hierarchy!
		}
		else if(currentLine.find(MD5_ANIM_BOUNDS) != std::string::npos) {
			LoadMD5AnimBounds(f,numLoadedBounds);		//Let's load in the AABBs!
		}
		else if(currentLine.find(MD5_ANIM_BASEFRAME) != std::string::npos) {
			LoadMD5AnimBaseFrame(f);					//Let's load in the base frame!
		}
		else if(currentLine.find(MD5_ANIM_FRAME) != std::string::npos) {
			LoadMD5AnimFrame(f,numLoadedFrames);		//Let's load in an animation frame!
		}
	}

	//If we get to here, we've loaded in everything from the file, so we can close it
	f.close();

	//If what we've loaded in does not equal what we /should/ have loaded in, we'll output an error
	//
	if(numLoadedFrames != numFrames || numLoadedJoints != numJoints || numLoadedBounds != numFrames) {
		std::cout << "MD5Anim file has incorrect data..." << std::endl;
	}
}

/*
Loads in the MD5Anim joint hierarchy. Uses the same ifstream as LoadMD5Anim, passed
by reference. We also pass a count variable by reference, which this function will
increment for every joint we load in. 
*/
void MD5Anim::LoadMD5AnimHierarchy( std::ifstream &from,unsigned int &count )	{
	/*
	The hierarchy section of the file should look something like this...

	hierarchy {
		"name"	parent flags frameIndex	//
		...more things
	}

	"hierarchy" is loaded in by LoadMD5Anim, so the first thing this function should see
	is a brace
	*/
	std::string tempLine; //Another temporary line to stream things into...

	do {
		from >> tempLine;	//Stream a line in

		if(tempLine == "{") {			//In a well-behaved MD5 file, the first line will be '{'
		}
		else if(tempLine[0] == '"'){	//It's a joint!
			//substr cuts out a section of a string, exclusive of the first and second parameter
			//positions (which should both be '"'
			joints[count].name = tempLine.substr(1,tempLine.find('"',1)-1);

			from >> joints[count].parent;
			from >> joints[count].flags;
			from >> joints[count].frameIndex;

			++count;
		}	
	}while(tempLine != "}");	//Hit an end bracket...
}

/*
Loads in the MD5Anim anim frames AABBs. Uses the same ifstream as LoadMD5Anim, passed
by reference. We also pass a count variable by reference, which this function will
increment for every joint we load in. 
*/
void MD5Anim::LoadMD5AnimBounds( std::ifstream &from,unsigned int &count  )	{
	/*
	The bounds section of the file should look like this:
	bounds {
		( min.x min.y min.z ) ( max.x max.y max.z )
		...more things
	}

	"bounds" is loaded in by LoadMD5Anim, so the first thing this function should see
	is a brace
	*/

	char skipChar;			//We skip the brackets by streaming them into this
	std::string tempLine;	//Another temporary line to stream things into...

	do {
		from >> tempLine;	

		if(tempLine == "{") {				//In a well-behaved MD5 file, the first line will be '{'
		}
		else if(tempLine[0] == '('){		//It's a bounding box!
			from >> bounds[count].min.x;
			from >> bounds[count].min.y;
			from >> bounds[count].min.z;

			from >> skipChar;				//Skip the ')'
			from >> skipChar;				//skip the '('

			from >> bounds[count].max.x;
			from >> bounds[count].max.y;
			from >> bounds[count].max.z;

			from >> skipChar;				//Skip the ')'

			++count;
		}
	}while(tempLine != "}");		//Hit an end bracket...
}

/*
Loads in the MD5Anim base frame, which consists of the default
positions and orientations of every joint in the mesh. 
Uses the same ifstream as LoadMD5Anim, passed by reference.
*/
void MD5Anim::LoadMD5AnimBaseFrame( std::ifstream &from )	{
	/*
	The baseframe section of the file should look like this:
	baseframe {
		( pos.x pos.y pos.z ) ( quat.x quat.y quat.z )
		...more things
	}

	"baseframe" is loaded in by LoadMD5Anim, so the first thing this function 
	should see is a brace
	*/

	char skipChar;			//We skip the brackets by streaming them into this
	std::string tempLine;	//Another temporary line to stream things into...

	/*
	We need to initialise enough space on the heap for every joint transform
	*/
	baseFrame.orientations	= new Quaternion[numJoints];
	baseFrame.positions		= new Vector3[numJoints];

	int current = 0;

	do {
		from >> tempLine;	

		if(tempLine == "{") {			//In a well-behaved MD5 file, the first line will be '{'
		}
		else if(tempLine[0] == '('){	//It's a base frame (probably)!
			from >> baseFrame.positions[current].x;
			from >> baseFrame.positions[current].y;
			from >> baseFrame.positions[current].z;

			from >> skipChar;	//End Bracket
			from >> skipChar;	//Begin Bracket

			from >> baseFrame.orientations[current].x;
			from >> baseFrame.orientations[current].y;
			from >> baseFrame.orientations[current].z;

			/*
			To save a tiny bit of space, the 4th component of the orientation
			quaternion is left out of the files. As we're dealing with unit length 
			quaternions (i.e they have a length of 1), the 4th component will be 
					sqrt of (1.0 - length of the other 3 components)
			*/

			baseFrame.orientations[current].GenerateW();
			baseFrame.orientations[current].Normalise();

			from >> skipChar;	//End Bracket
			++current;
		}
	}while(tempLine != "}");
}

/*
Loads in an MD5Frane, which consists of the differences from the baseframe
of the positions and orientations of every joint in the mesh. 
Uses the same ifstream as LoadMD5Anim, passed by reference.
We also pass a count variable by reference, which this function will
increment for every joint we load in. 
*/
void MD5Anim::LoadMD5AnimFrame( std::ifstream &from, unsigned int &count)	{
	/*
	Each animframe section of the file should look like this:
	frame framenum {
		linear array of floating point values
		...
	}

	"frame" is loaded in by LoadMD5Anim, so the first thing this function 
	should see is the framenum
	*/

	std::string tempLine;	//Another temporary line to stream things into...

	int frameNum;
	from >> frameNum;	//Stream in the current frame number

	/*
	Every frame has the same number of 'delta' floats - so even if a joint
	is only modified in a single frame, it will have a delta value in every
	frame.
	*/
	frames[frameNum].components = new float[numAnimatedComponents];

	from >> tempLine;	//Load in the next line, which /should/ be "{"

	if(tempLine == "{") {//In a well-behaved MD5 file, the first line will be '{'
		for(unsigned int i = 0; i < numAnimatedComponents; ++i) {
			//stream in the delta values of the current animation frame
			from >> frames[frameNum].components[i];
		}
		from >> tempLine;	// Should be '}'
		++count;
	}
}


//Transforms the passed in skeleton to the correct positions and
//orientations for the desired frame
void	MD5Anim::TransformSkeleton(MD5Skeleton &skel, unsigned int frameNum) {
	/*
	Here's the most important function of the MD5Anim class. This transforms an input
	skeleton's joints (generally this will be the 'working' skeleton from an MD5Mesh instance)
	to the required transforms to represent the desired frame of animation
	*/

	if(frameNum > numFrames) {	//This probably shouldn't ever happen!
		return;
	}

	//Grab a reference to the frame data for the relevant frame
	MD5Frame&frame = frames[frameNum];

	//For each joint in the animation
	for(unsigned int i = 0; i < numJoints; ++i) {
		//Grab COPIES of the position and orientation of the baseframe joint
		Vector3		animPos	 = baseFrame.positions[i];
		Quaternion  animQuat = baseFrame.orientations[i];

		/*
		Each frame has a number of 'delta' components, and each joint
		uses a number of these components to update its position and
		orientation. Whether or not each value is updated or not is
		determined by the joints flags variable. The starting
		component for each joint is determined by the frameIndex value
		of the joint.

		For each value of the joint (ie its 3 position values and its 3
		orientation components (we don't bother with the 4th as we can 
		reconstruct it) we check the flags to see if it should be updated,
		update it if necessary, and increment a counter so we access the
		next component.

		*/

		int j = 0;

		if (joints[i].flags & MD5_ANIM_XPOS) {//X component of Position
			animPos.x = frame.components[joints[i].frameIndex+j];
			++j;
		}
	
		if (joints[i].flags & MD5_ANIM_YPOS) {//Y component of Position
			animPos.y = frame.components[joints[i].frameIndex+j];
			++j;
		}
	
		if (joints[i].flags & MD5_ANIM_ZPOS) {//Z component of Position
			animPos.z = frame.components[joints[i].frameIndex+j];
			++j;
		}
		
		if (joints[i].flags & MD5_ANIM_XQUAT) {//X component of Orientation
			animQuat.x = frame.components[joints[i].frameIndex+j];
			++j;
		}
		
		if (joints[i].flags & MD5_ANIM_YQUAT) {//Y component of Orientation
			animQuat.y = frame.components[joints[i].frameIndex+j];
			++j;
		}
		
		if (joints[i].flags & MD5_ANIM_ZQUAT) {//Z component of Orientation
			animQuat.z = frame.components[joints[i].frameIndex+j];
			++j;
		}

		animQuat.GenerateW(); //We only get updated x,y,z so must generate W again...
		animQuat.Normalise(); //And we should probably normalise it, too, to keep to unit length

		//now we have a copy of the baseframe joint transformed to the animation pose, we can start
		//applying it to the input skeleton.

		//First, let's get a reference to the skeleton joint equating to the current baseframe joint
		MD5Joint &skelJoint = skel.joints[i];

		//I'm fairly sure this doesn't ever actually change...
		skelJoint.parent	= joints[i].parent;
		skelJoint.forceWorld = false;

		//We'll set its position and orientation to the transformed baseframe variables

		skelJoint.position		= animPos;
		skelJoint.orientation	= animQuat;	

		//Now to set the local transform of the current joint. We start by turning the orientation
		//quaternion into a Matrix4, then we set the resulting matrix translation to the
		//transformed baseframe position

		skelJoint.localTransform		= animQuat.ToMatrix4();
		skelJoint.localTransform.SetPositionVector(animPos);

		//If the joint has no parent (determined by a negative parent variable) we need to 
		//transform the joint's transform to the correct rotation, using the conversion matrix
		if(skelJoint.parent < 0) {	//Base Joint, so we're done
			skelJoint.transform = MD5FileData::conversionMatrix * skelJoint.localTransform;
		}
		else{	
			//If this joint /does/ have a parent, we transform the joint's transform by its
			//parent transform. Note that we don't have to transform it by the conversion matrix
			//again, as the parent node will already contain it, due to being propagated from 
			//the root node. Matrices are fun!
			MD5Joint &parent = skel.joints[skelJoint.parent];
			skelJoint.transform = parent.transform * skelJoint.localTransform;
		}
	}
}
#endif
#endif